#include <jni.h>
#include <android/log.h>
#include <string.h>
#include "bmtp_bmtp_JBmtp.h"
#include "bmtp.h"

#ifdef __cplusplus
extern "C" {
#endif

#define LOG_TAG "JNIDEBUG"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)

BMTP* bmtp = 0;
JavaVM *bmtp_jvm = 0;
jclass bmtp_class = 0;
jobject bmtp_obj = 0;

void cb_bmtp_on_open(BMTP* bmtp) {
    int status;
    JNIEnv *env;
    int isAttached = 0;
   
    status = (*bmtp_jvm)->GetEnv(bmtp_jvm, (void **) &env, JNI_VERSION_1_4);
    if(status < 0) {
        LOGE("callback_handler: failed to get JNI environment, "
             "assuming native thread");
        status = (*bmtp_jvm)->AttachCurrentThread(bmtp_jvm, &env, 0);
        if(status < 0) {
            LOGE("callback_handler: failed to attach "
                 "current thread");
            return;
        }
        isAttached = 1;
    }

    jmethodID method = (*env)->GetMethodID(env,bmtp_class,"onOpen","()V");
    if(!method) {
        LOGE("callback_handler: failed to get method ID");
        if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
        return;
    }

    (*env)->CallVoidMethod(env,bmtp_obj,method);

    if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
}

void cb_bmtp_on_close(BMTP* bmtp) {
    int status;
    JNIEnv *env;
    int isAttached = 0;
   
    status = (*bmtp_jvm)->GetEnv(bmtp_jvm, (void **) &env, JNI_VERSION_1_4);
    if(status < 0) {
        LOGE("callback_handler: failed to get JNI environment, "
             "assuming native thread");
        status = (*bmtp_jvm)->AttachCurrentThread(bmtp_jvm, &env, 0);
        if(status < 0) {
            LOGE("callback_handler: failed to attach "
                 "current thread");
            return;
        }
        isAttached = 1;
    }

    jmethodID method = (*env)->GetMethodID(env,bmtp_class,"onClose","()V");
    if(!method) {
        LOGE("callback_handler: failed to get method ID");
        if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
        return;
    }

    (*env)->CallVoidMethod(env,bmtp_obj,method);

    if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
}

void cb_bmtp_on_error(BMTP* bmtp, int err_no) {
    int status;
    JNIEnv *env;
    int isAttached = 0;
   
    status = (*bmtp_jvm)->GetEnv(bmtp_jvm, (void **) &env, JNI_VERSION_1_4);
    if(status < 0) {
        LOGE("callback_handler: failed to get JNI environment, "
             "assuming native thread");
        status = (*bmtp_jvm)->AttachCurrentThread(bmtp_jvm, &env, 0);
        if(status < 0) {
            LOGE("callback_handler: failed to attach "
                 "current thread");
            return;
        }
        isAttached = 1;
    }

    jmethodID method = (*env)->GetMethodID(env,bmtp_class,"onError","(I)V");
    if(!method) {
        LOGE("callback_handler: failed to get method ID");
        if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
        return;
    }

    (*env)->CallVoidMethod(env,bmtp_obj,method,err_no);

    if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
}

void cb_bmtp_on_message(BMTP* bmtp, const char *data, int len) {
    int status;
    JNIEnv *env;
    int isAttached = 0;
   
    status = (*bmtp_jvm)->GetEnv(bmtp_jvm, (void **) &env, JNI_VERSION_1_4);
    if(status < 0) {
        LOGE("callback_handler: failed to get JNI environment, "
             "assuming native thread");
        status = (*bmtp_jvm)->AttachCurrentThread(bmtp_jvm, &env, 0);
        if(status < 0) {
            LOGE("callback_handler: failed to attach "
                 "current thread");
            return;
        }
        isAttached = 1;
    }

    jmethodID method = (*env)->GetMethodID(env,bmtp_class,"onMessage","([B)V");
    if(!method) {
        LOGE("callback_handler: failed to get method ID");
        if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
        return;
    }

    jbyteArray barr = (*env)->NewByteArray(env,len);
    (*env)->SetByteArrayRegion(env,barr, 0, len, (jbyte*)data);

    (*env)->CallVoidMethod(env,bmtp_obj,method,barr);

    if(isAttached) (*bmtp_jvm)->DetachCurrentThread(bmtp_jvm);
}

JNIEXPORT jint JNI_OnLoad
        (JavaVM* vm, void *reserved)
{
    bmtp_jvm = vm;
    
    JNIEnv* env;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK)
        return -1;
    
    bmtp_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "bmtp/bmtp/JBmtp"));

    return JNI_VERSION_1_4;
}

/*
 * Class:     bmtp_bmtp_JBmtp
 * Method:    open
 * Signature: (Ljava/lang/String;I)Z
 */
JNIEXPORT jboolean JNICALL Java_bmtp_bmtp_JBmtp_open
        (JNIEnv *env, jobject obj, jstring ip, jint port)
{
    static int bmtp_is_init = 0;
    if(!bmtp_is_init) {
        bmtp_init();
    }
    
    if(!bmtp) {
        const char *c_ip = (*env)->GetStringUTFChars(env, ip, 0);
        if(c_ip == 0) {
            return JNI_FALSE;
        }
        LOGV("%s:%d", c_ip, port);
        bmtp = bmtp_new(c_ip, port);
        LOGV("%p", bmtp);
        (*env)->ReleaseStringUTFChars(env, ip, c_ip);
        
        if(!bmtp) {
            return JNI_FALSE;
        }
    } else {
        return JNI_FALSE;
    }
    
    bmtp_obj = (*env)->NewGlobalRef(env, obj);
    
    bmtp_set_on_open(bmtp, cb_bmtp_on_open);
    bmtp_set_on_close(bmtp, cb_bmtp_on_close);
    bmtp_set_on_error(bmtp, cb_bmtp_on_error);
    bmtp_set_on_message(bmtp, cb_bmtp_on_message);
    bmtp_open(bmtp);

    return JNI_TRUE;
}

/*
 * Class:     bmtp_bmtp_JBmtp
 * Method:    pub
 * Signature: (Ljava/lang/String;I)Z
 */
JNIEXPORT jboolean JNICALL Java_bmtp_bmtp_JBmtp_pub
        (JNIEnv *env, jobject obj, jstring data, jint qos)
{
    if(!bmtp) {
        return JNI_FALSE;
    }

    jboolean ret;

    const char *c_data = (*env)->GetStringUTFChars(env, data, 0);

    if(bmtp_pub(bmtp, c_data, strlen(c_data), qos) == 0) {
        ret = JNI_TRUE;
    } else {
        ret = JNI_FALSE;
    }

    (*env)->ReleaseStringUTFChars(env, data, c_data);
    
    return ret;
}

/*
 * Class:     bmtp_bmtp_JBmtp
 * Method:    sub
 * Signature: (J)Z
 */
JNIEXPORT jboolean JNICALL Java_bmtp_bmtp_JBmtp_sub
        (JNIEnv *env, jobject obj, jlong channel_id)
{
    if(!bmtp) {
        return JNI_FALSE;
    }

    if(bmtp_sub(bmtp, channel_id) == 0) {
        return JNI_TRUE;
    } else {
        return JNI_FALSE;
    }
}

#ifdef __cplusplus
}
#endif
